package com.qf.leadnewswemedia.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qf.leadnewscommon.content_security.ScanOperator;
import com.qf.leadnewscommon.content_security.Suggestion;
import com.qf.leadnewscommon.minio.MinioOperator;
import com.qf.leadnewsmodel.consts.KafkaConst;
import com.qf.leadnewsmodel.dtos.PageResponseResult;
import com.qf.leadnewsmodel.dtos.ResponseResult;
import com.qf.leadnewsmodel.enums.AppHttpCodeEnum;
import com.qf.leadnewsmodel.enums.TaskTypeEnum;
import com.qf.leadnewsmodel.pojos.schedule.Task;
import com.qf.leadnewsutils.commons.ProtostuffUtil;
import com.qf.leadnewsutils.commons.ThreadLocalUtil;
import com.qf.leadnewswemedia.feign.ScheduleFeignApi;
import com.qf.leadnewswemedia.mapper.WmMaterialMapper;
import com.qf.leadnewswemedia.mapper.WmNewsMapper;
import com.qf.leadnewswemedia.mapper.WmNewsMaterialMapper;
import com.qf.leadnewswemedia.model.dtos.NewsPageRequestDto;
import com.qf.leadnewswemedia.model.dtos.WmNewsDto;
import com.qf.leadnewswemedia.model.pojos.WmMaterial;
import com.qf.leadnewsmodel.pojos.wemedia.WmNews;
import com.qf.leadnewswemedia.model.pojos.WmNewsMaterial;
import com.qf.leadnewswemedia.service.WmNewsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class WmNewsServiceImpl extends ServiceImpl<WmNewsMapper, WmNews> implements WmNewsService {

    @Autowired
    private WmNewsMaterialMapper newsMaterialMapper;

    @Autowired
    private WmMaterialMapper materialMapper;


    @Override
    @Transactional
    public ResponseResult submit(WmNewsDto wmNewsDto) throws UnsupportedEncodingException {

        //所有业务方法都需要在登录状态下完成，可以是使用AOP （自己完成）
//        Long uid = ThreadLocalUtil.getUid();
//        if(uid == null || uid == 0){
//            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
//        }

        if (wmNewsDto == null || StringUtils.isBlank(wmNewsDto.getContent())){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        WmNews wmNews = new WmNews();
        BeanUtils.copyProperties(wmNewsDto,wmNews);

        //设置wmNewsDto里面没有的数据
        wmNews.setUserId(Integer.parseInt(ThreadLocalUtil.getUid()+""));
        //todo:封面图片自动获取,后续处理.这里我们先设置为无图状态
        if (wmNewsDto.getType() == -1){
            wmNews.setType((short) 0);
        }
        wmNews.setCreatedTime(new Date());
        wmNews.setSubmitedTime(new Date());

        if (wmNewsDto.getPublishTime() == null){
            //非定时发布,发布时间设置为当前系统时间
            wmNews.setPublishTime(new Date());
        }
        //默认上架
        wmNews.setEnable((short) 1);
        String imagesStr = list2String(wmNewsDto.getImages(), ",");
        wmNews.setImages(imagesStr);

        //保存到nes表  (文章表的数据已经完成了添加，后续我们需要将文章中的素材信息和素材表进行绑定)
        // todo: 这里我们直接添加记录是否合适？？  有可能是一个更新操作. 如果传递进来的数据有id--更新，没有id--新增
        saveOrUpdate(wmNews);

        //如果是保存草稿，该文章发布操作到此结束
        Short status = wmNewsDto.getStatus();
        if (status == 0){
            return ResponseResult.errorResult(AppHttpCodeEnum.SUCCESS);
        }

        //如果不是保存草稿，我们还需要将文章中素材信息和素材表进行绑定
        //[{"type":"text","value":"mongodb是一款高性能内存存储的非关系型数据，数据是以文档形式存储的，一般我们用来存储如关系的大量数据，比如文章的评论信息......."},{"type":"image","value":"http://localhost:9005/leadnews/2023/02/09/d8fe87fdf2534c67a3b9f67ae1214f3f.jpg"},{"type":"text","value":"sdsfd,,,,,,,"},{"type":"text","value":"请在这里输入正文"}]
        String content = wmNewsDto.getContent();
        List<String> images = extractImages(content);

        handlerContentAndMaterial(wmNewsDto, wmNews, images);

        //进行文章信息的审核(文本内容审核 + 图片审核) -- 明天做
        //不再是实时发布，应该是根据用户选择的发布时间，进行文章发布
        //autoScan(wmNews);

        delayScan(wmNews);

        //如果文章不是实时发布，我们需要在指定的未来时间点再进行审核和文章数据同步
        /*
            实现方案：
                 延迟消息 （rabbitmq - 死信+过期时间，只检查头部消息，需要添加rabbitmq的延迟消息插件）
                 DelayQueue -- java提供的延迟队列，数据是在内存中的，不安全，不具备分布式能力
         */

        return ResponseResult.errorResult(AppHttpCodeEnum.SUCCESS);
    }

    @Autowired
    private ScheduleFeignApi scheduleFeignApi;
    /**
     * 延迟发布文章
     * @param wmNews
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Async
    public void delayScan(WmNews wmNews) {
        //将文章信息添加到调度服务的数据库中
        Task task = new Task();
        task.setExecuteTime(wmNews.getPublishTime().getTime());
        task.setTaskType(TaskTypeEnum.NEWS_SCAN_TIME.getTaskType());
        task.setPriority(TaskTypeEnum.NEWS_SCAN_TIME.getPriority());
        //任务的参数放什么？？？
        String wmNewsId = wmNews.getId() + "";
        byte[] bytes = ProtostuffUtil.serialize(wmNewsId);
        task.setParameters(bytes);

        scheduleFeignApi.addTask(task);
    }

    @Autowired
    private ScanOperator scanOperator;

    @Autowired
    private MinioOperator minioOperator;

    @Autowired
    private KafkaTemplate kafkaTemplate;

    /**
     * 文章自动审核方法
     * propagation = Propagation.REQUIRES_NEW  -- 新建事务（这个方法执行的操作，不影响调用这个方法的事务结果）
     * @param wmNews
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Async //这个方法在异步线程中执行
    public void autoScan(WmNews wmNews) throws UnsupportedEncodingException {
        //审核文本内容（文章标题+文章内容）
        /*String content = wmNews.getTitle() + extractTexts(wmNews.getContent());
        Map<String, Suggestion> textSuggestionMap = scanOperator.textScan(content);
        Set<String> textSencesKeys = textSuggestionMap.keySet();
        for (String textSencesKey : textSencesKeys) {
            Suggestion suggestion = textSuggestionMap.get(textSencesKey);
            String suggestionStr = suggestion.getSuggestion();
            if ("review".equals(suggestionStr)){
                //人工审核
                wmNews.setStatus((short) 3);
                wmNews.setReason(suggestion.getLabel());

                //更新数据库
                updateById(wmNews);
                return;
            }else if ("block".equals(suggestionStr)){
                //审核不通过
                wmNews.setStatus((short) 2);
                wmNews.setReason(suggestion.getLabel());

                //更新数据库
                updateById(wmNews);
                return;
            }
        }

        //审核图片内容（文章内容图片+封面图片 -- 有可能重复，去重）
        String fmImagesStr = wmNews.getImages();
        List<String> fmImages = null;
        if (StringUtils.isNotBlank(fmImagesStr)){
            String[] split = fmImagesStr.split(",");
            fmImages = Arrays.asList(split);
        }
        List<String> contentImages = extractImages(wmNews.getContent());

        Set<String> images = new HashSet<>();
        images.addAll(fmImages);
        images.addAll(contentImages);

        for (String imageUrl : images) {
            byte[] bytes = minioOperator.downLoadFile(imageUrl);
            Map<String, Suggestion> imageSuggestionMap = scanOperator.iamgeScan(bytes);

            Set<String> imageSencesKeys = imageSuggestionMap.keySet();
            for (String imageSencesKey : imageSencesKeys) {
                Suggestion suggestion = imageSuggestionMap.get(imageSencesKey);
                String suggestionStr = suggestion.getSuggestion();
                if ("review".equals(suggestionStr)){
                    //人工审核
                    wmNews.setStatus((short) 3);
                    wmNews.setReason(suggestion.getLabel());

                    //更新数据库
                    updateById(wmNews);
                    return;
                }else if ("block".equals(suggestionStr)){
                    //审核不通过
                    wmNews.setStatus((short) 2);
                    wmNews.setReason(suggestion.getLabel());

                    //更新数据库
                    updateById(wmNews);
                    return;
                }
            }
        }*/

        //审核通过
        wmNews.setStatus((short) 8);
        wmNews.setReason("审核通过");
        updateById(wmNews);

        //审核通过，将文章进行发布 -- 设计article服务的数据同步（同步调用 或者 异步调用） -- 明天做
        //发布文章这个操作不应该影响上述内容审核的事务操作
        try {
            log.info("====kafka发送消息，newsId:{}=====",wmNews.getId());
            kafkaTemplate.send(KafkaConst.PUBLISH_WMNEWS_TOPIC, wmNews.getId() + "");

            log.info("====kafka发送消息成功，newsId:{}=====",wmNews.getId());
        }catch (Exception e){
            //如果发布文章失败，不影响上述审核业务。后续我们可以通过定时任务进行重新发布
            e.printStackTrace();
        }
    }

    @Override
    public ResponseResult pageList(NewsPageRequestDto pageRequestDto) {
        if (pageRequestDto == null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //校验分页参数
        pageRequestDto.checkParam();

        //分页参数
        IPage<WmNews> page = new Page<>(pageRequestDto.getPage(),pageRequestDto.getSize());
        //条件参数
        LambdaQueryWrapper<WmNews> qw = new LambdaQueryWrapper<>();
        //发布状态
        Integer status = pageRequestDto.getStatus();
        qw.eq(status != null,WmNews::getStatus,status);

        //发布频道
        Integer channelId = pageRequestDto.getChannelId();
        qw.eq(channelId!=null,WmNews::getChannelId,channelId);

        //发布时间
        Date beginPubDate = pageRequestDto.getBeginPubDate();
        Date endPubDate = pageRequestDto.getEndPubDate();
        qw.between(beginPubDate!=null&&endPubDate!=null,WmNews::getPublishTime,beginPubDate,endPubDate);

        //搜索关键字(这里为了后期构建索引，我们限制只能根据右匹配搜索)
        String keyword = pageRequestDto.getKeyword();
        qw.likeRight(StringUtils.isNotBlank(keyword),WmNews::getTitle,keyword);

        //根据发布时间倒序排列
        qw.orderByDesc(WmNews::getPublishTime);

        page(page,qw);

        List<WmNews> records = page.getRecords();
        PageResponseResult pageResponseResult = new PageResponseResult(page.getCurrent(),page.getSize(),page.getTotal());
        pageResponseResult.setData(records);

        return pageResponseResult;
    }

    /**
     * 处理文章图片和素材关系的方法
     * @param wmNewsDto  前台传递过来的文章信息
     * @param wmNews 新增或者更新后的数据库文章内容对象
     * @param images 从内容中提取出来的图片列表
     */
    private void handlerContentAndMaterial(WmNewsDto wmNewsDto, WmNews wmNews, List<String> images) {
        //先将当前新闻的关系在素材关系表中删除
        LambdaQueryWrapper<WmNewsMaterial> qw = new LambdaQueryWrapper<>();
        qw.eq(WmNewsMaterial::getNewsId,wmNews.getId());
        newsMaterialMapper.delete(qw);

        //保存内容图片和素材表的关系
        saveMaterialRelation(images, wmNews.getId(),0);

        //封面图片处理
        List<String> fmImages = wmNewsDto.getImages(); //前台传递过来的封面图片
        //封面是自动，封面图片需要从内容中提取出来
        if (wmNewsDto.getType() == -1){
            int size = images.size();
            if (size >= 3){
                fmImages = images.stream().limit(3).collect(Collectors.toList());
                wmNews.setType((short) 3);
            }else if (size >= 1){
                fmImages = images.stream().limit(1).collect(Collectors.toList());
                wmNews.setType((short) 1);
            }
        }

        if (fmImages != null && fmImages.size() > 0){
            //更新wmnews的type和images字段
            String s = list2String(fmImages, ",");
            wmNews.setImages(s);

            //更新wmnews
            updateById(wmNews);

            saveMaterialRelation(fmImages, wmNews.getId(),1);
        }
    }

    /**
     * 保存素材关系
     * @param images  素材图片地址
     * @param newsId 新闻id
     * @param type 素材关系的类型  0 表示内容素材， 1 表示封面素材
     */
    private void saveMaterialRelation(List<String> images, Integer newsId, int type) {
        List<WmNewsMaterial> wmNewsMaterials = new ArrayList<>();
        for (String image : images) {
            WmNewsMaterial wmNewsMaterial = new WmNewsMaterial();

            LambdaQueryWrapper<WmMaterial> qw = new LambdaQueryWrapper<>();
            qw.eq(WmMaterial::getUrl,image);
            WmMaterial material = materialMapper.selectOne(qw);

            wmNewsMaterial.setMaterialId(material.getId());
            wmNewsMaterial.setNewsId(newsId);
            wmNewsMaterial.setType((short) type);

            wmNewsMaterials.add(wmNewsMaterial);
        }

        //批量添加
        newsMaterialMapper.batchInsert(wmNewsMaterials);
    }

    /**
     * 从上传的内容中提取图片链接
     * @param content
     * @return
     */
    private List<String> extractImages(String content) {
        if (StringUtils.isNotBlank(content)){
            List<Map> maps = JSON.parseArray(content, Map.class);

            List<String> imageUrls = new ArrayList<>();
            for (Map map : maps) {
                Object type = map.get("type");
                if ("image".equals(type)){
                    String imageUrl = (String) map.get("value");
                    imageUrls.add(imageUrl);
                }
            }

            return imageUrls;
        }
        return null;
    }
    /**
     * 从上传的内容中提取内容
     * @param content
     * @return
     */
    private String extractTexts(String content) {
        if (StringUtils.isNotBlank(content)){
            StringBuilder sb = new StringBuilder();
            List<Map> maps = JSON.parseArray(content, Map.class);

            List<String> imageUrls = new ArrayList<>();
            for (Map map : maps) {
                Object type = map.get("type");
                if ("text".equals(type)){
                    String text = (String) map.get("value");
                    sb.append(text);
                }
            }

            return sb.toString();
        }
        return null;
    }

    /**
     * 将列表数据，转成字符串格式内容，多个列表元素之间，通过指定的分隔符进行分割
     * @param images
     * @param seperator
     * @return
     */
    private String list2String(List<String> images,String seperator) {
        if(images != null && images.size() > 0){
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < images.size(); i++) {
                sb.append(images.get(i));
                if (i != images.size()-1){
                    sb.append(seperator);
                }
            }
            return sb.toString();
        }

        return null;
    }
}
